
package net.qiqbframework.messaging.annotation;

import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.ref.WeakReference;
import java.util.*;

import static java.util.ServiceLoader.load;

/**
 *
 */
@Slf4j
public final class ClasspathHandlerDefinition {

    private static final Object MONITOR = new Object();
    private static final Map<ClassLoader, WeakReference<MultiHandlerDefinition>> FACTORIES = new WeakHashMap<>();

    private ClasspathHandlerDefinition() {
        // not meant to be publicly instantiated
    }

    /**
     * Creates an instance for the given {@code clazz}. Effectively, the class loader of the given class is used to
     * locate implementations.
     *
     * @param clazz The class for which the handler definition must be returned
     * @return a MultiHandlerDefinition that can create handlers for the given class
     */
    public static MultiHandlerDefinition forClass(Class<?> clazz) {
        return forClassLoader(clazz == null ? null : clazz.getClassLoader());
    }

    /**
     * Creates an instance using the given {@code classLoader}. Implementations are located using this class loader.
     *
     * @param classLoader The class loader to locate the implementations with
     * @return a MultiHandlerDefinition instance using the given classLoader
     */
    public static MultiHandlerDefinition forClassLoader(ClassLoader classLoader) {
        synchronized (MONITOR) {
            MultiHandlerDefinition handlerDefinition;
            if (!FACTORIES.containsKey(classLoader)) {
                handlerDefinition = MultiHandlerDefinition.ordered(findDelegates(classLoader));
                FACTORIES.put(classLoader, new WeakReference<>(handlerDefinition));
                return handlerDefinition;
            }
            handlerDefinition = FACTORIES.get(classLoader).get();
            if (handlerDefinition == null) {
                handlerDefinition = MultiHandlerDefinition.ordered(findDelegates(classLoader));
                FACTORIES.put(classLoader, new WeakReference<>(handlerDefinition));
            }
            return handlerDefinition;
        }
    }

    private static MultiHandlerDefinition findDelegates(ClassLoader classLoader) {
        Iterator<MessageHandlingMemberFactory> iterator = load(MessageHandlingMemberFactory.class, classLoader == null ?
                Thread.currentThread().getContextClassLoader() : classLoader).iterator();

        final List<MessageHandlingMemberFactory> definitions = new ArrayList<>();
        while (iterator.hasNext()) {
            try {
                MessageHandlingMemberFactory factory = iterator.next();
                definitions.add(factory);
            } catch (ServiceConfigurationError e) {
                log.info("HandlerDefinition instance ignored, as one of the required classes is not availableon the classpath: {}", e.getMessage());
            } catch (NoClassDefFoundError e) {
                log.info("HandlerDefinition instance ignored. It relies on a class that cannot be found: {}",
                            e.getMessage());
            }
        }
        return new MultiHandlerDefinition(definitions);
    }
}
